package pl.edu.mimuw.irs.rcp.controller.editor.resources;

import java.util.LinkedHashSet;
import java.util.Set;

import org.eclipse.gef.EditDomain;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import pl.edu.mimuw.irs.core.model.design.resource.IHolder;
import pl.edu.mimuw.irs.core.model.design.resource.IMember;
import pl.edu.mimuw.irs.core.model.design.resource.Resource;
import pl.edu.mimuw.irs.core.model.design.resource.Resources;
import pl.edu.mimuw.irs.core.model.design.resource.Role;
import pl.edu.mimuw.irs.core.model.design.resource.RoleInstance;
import pl.edu.mimuw.irs.rcp.controller.editor.roles.AdaptableRoleInstanceWrapper;
import pl.edu.mimuw.irs.rcp.controller.editor.util.IAdaptableDataWrapper;
import pl.edu.mimuw.irs.rcp.controller.editor.util.command.AddMemberCommand;
import pl.edu.mimuw.irs.rcp.controller.editor.util.command.DeleteMemberCommand;
import pl.edu.mimuw.irs.rcp.util.Icon;
import pl.edu.mimuw.irs.rcp.util.dnd.ResourcesTransfer;
import pl.edu.mimuw.irs.rcp.util.dnd.RolesInstancesTransfer;
import pl.edu.mimuw.irs.rcp.util.dnd.RolesTransfer;

public class ResourcesTreeWrapper {

	// *** Attributes

	private Resources resources;
	private EditDomain editDomain;
	private TreeViewer treeViewer;
	public Resources getResources() {
		return resources;
	}
	public EditDomain getEditDomain() {
		return editDomain;
	}
	public TreeViewer getTreeViewer() {
		return treeViewer;
	}
	public CommandStack getCommandStack() {
		return getEditDomain().getCommandStack();
	}
	
	// *** Constructors

	public ResourcesTreeWrapper(Composite parent, int style, 
			Resources resources, EditDomain editDomain) {
		treeViewer = new TreeViewer(parent, style);
		this.resources = resources;
		this.editDomain = editDomain;
		
		redrawTree();
		
		// Drag'N'Drop
		addDragSource();
		addDropTarget();
	}
	
	// *** Methods
	
	public void redrawTree() {
		Tree tree = treeViewer.getTree();
		
		Set<Resource> expanded = new LinkedHashSet<Resource>();
		for (TreeItem resourceItem : tree.getItems()) {
			if (!(resourceItem.getData() instanceof AdaptableResourceWrapper)) continue;
			AdaptableResourceWrapper wrapper = (AdaptableResourceWrapper) resourceItem.getData();
			if (resourceItem.getExpanded()) {
				expanded.add(wrapper.getData());
			}
		}
		
		tree.removeAll();
		for (Resource resource : getResources()) {
			TreeItem parent = new TreeItem(tree, SWT.BORDER);
			parent.setText(new String[] {resource.getName()});
			parent.setImage(Icon.PERSON.getImage());
			// Store resource in tree item
			Object resourceWrapper = new AdaptableResourceWrapper(resource, editDomain); 
			parent.setData(resourceWrapper);
			for (RoleInstance role : resource.getRolesMap().values()) {
				TreeItem child = new TreeItem(parent, SWT.BORDER);
				child.setText(new String[] {role.getRole().getName()});
				
				if (!role.isPreserve()) child.setImage(Icon.YELLOW_SKILL.getImage());
				else if (role.getCalculatedModifier() != null) child.setImage(Icon.GREEN_SKILL.getImage());
				else child.setImage(Icon.BLUE_SKILL.getImage());
				// Store resource in tree item
				Object roleWrapper = new AdaptableRoleInstanceWrapper(role, resource, editDomain); 
				child.setData(roleWrapper);
			}
			if (expanded.contains(resource)) parent.setExpanded(true);
		}

		tree.redraw();
	}
	
	private void addDragSource() {
		final Tree tree = treeViewer.getTree();
		int operations = DND.DROP_MOVE | DND.DROP_COPY;
		final DragSource source = new DragSource(tree, operations);
		
		Transfer[] types = new Transfer[] { ResourcesTransfer.getInstance(), RolesInstancesTransfer.getInstance() };
	 	source.setTransfer(types);
		
	 	source.addDragListener(new DragSourceListener() {
	 		private TransferData lastDataType; 
			private IMember[] lastObjects; 
	 		@SuppressWarnings("unchecked")
			public void dragStart(DragSourceEvent event) {
	 			if (tree.getSelection().length == 0) {
	 				event.doit = false;
	 			} else {
	 				Object[] selection = tree.getSelection();
	 				boolean rolesInstances = true;
	 				boolean resources = true;
	 				/*
	 				 * Make sure all are of one type that we can handle.
	 				 */
	 				for (Object object : selection) {
	 					TreeItem item = (TreeItem) object;
	 					IAdaptableDataWrapper wrapper = (IAdaptableDataWrapper) item.getData();
	 					if (!(wrapper.getData() instanceof RoleInstance)) rolesInstances = false;
	 					if (!(wrapper.getData() instanceof Resource)) resources = false;
	 					if (!rolesInstances && !resources) {
	 						event.doit = false;
	 						break;
	 					}
	 				}
	 				
	 				if (rolesInstances) source.setTransfer(new Transfer[] { RolesInstancesTransfer.getInstance() });
	 				else source.setTransfer(new Transfer[] { ResourcesTransfer.getInstance() });
	 			}
	 		}
	 		public void dragSetData(DragSourceEvent event) {
	 			// Provide the data of the requested type.
	 			lastDataType = event.dataType;
	 			if (RolesInstancesTransfer.getInstance().isSupportedType(event.dataType)) {
	 				RoleInstance[] roles = new RoleInstance[tree.getSelection().length];
	 				Object[] selection = tree.getSelection();
	 				for (int i = 0; i < selection.length; i++) {
	 					TreeItem item = (TreeItem)selection[i];
	 					AdaptableRoleInstanceWrapper wrapper = 
	 						(AdaptableRoleInstanceWrapper) item.getData();
	 					roles[i] = wrapper.getData();
	 				}
	 				lastObjects = roles;
	 				event.data = roles;
	 			} else if (ResourcesTransfer.getInstance().isSupportedType(event.dataType)) {
	 				Resource[] resources = new Resource[tree.getSelection().length];
	 				Object[] selection = tree.getSelection();
	 				for (int i = 0; i < selection.length; i++) {
	 					TreeItem item = (TreeItem)selection[i];
	 					AdaptableResourceWrapper wrapper = 
	 						(AdaptableResourceWrapper) item.getData();
	 					resources[i] = wrapper.getData();
	 				}
	 				lastObjects = resources;
	 				event.data = resources;
	 			}
	 		}
	 		public void dragFinished(DragSourceEvent event) {
		 	    // If a move operation has been performed, remove the data
		 	    // from the source
	 			if (event.doit == false) return;
	 			if ((event.detail & DND.DROP_MOVE) == 0) return;
	 			if (RolesInstancesTransfer.getInstance().isSupportedType(lastDataType) ||
	 					ResourcesTransfer.getInstance().isSupportedType(lastDataType)) {
	 				for (int i = 0; i < lastObjects.length; i++) {
	 					delete(lastObjects[i]);
	 				}
	 			}
			}
		});
	}
	
	private void addDropTarget() {
		Tree tree = treeViewer.getTree();
		DropTarget target = new DropTarget(tree, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK | DND.DROP_DEFAULT);
		Transfer[] types = new Transfer[] { RolesTransfer.getInstance(), RolesInstancesTransfer.getInstance(), ResourcesTransfer.getInstance() };
		target.setTransfer(types);
		target.addDropListener(new DropTargetAdapter() {
			
			public void dragEnter(DropTargetEvent event) {
				if (event.detail == DND.DROP_DEFAULT) {
					event.detail = (event.operations & DND.DROP_LINK) != 0 ? DND.DROP_LINK
							: (event.operations & DND.DROP_MOVE) != 0 ? DND.DROP_MOVE
									: (event.operations & DND.DROP_COPY) != 0 ? DND.DROP_COPY
											: DND.DROP_NONE;
				}
				for (int i = 0, n = event.dataTypes.length; i < n; i++) {
					if (event.currentDataType == null) {
						if (RolesTransfer.getInstance().isSupportedType(
								event.dataTypes[i])) {
							event.currentDataType = event.dataTypes[i];
						}
					}
					if (ResourcesTransfer.getInstance().isSupportedType(
							event.dataTypes[i])) {
						event.currentDataType = event.dataTypes[i];
					}
				}
			}

			public void dragOver(DropTargetEvent event) {
				event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL;
			}

			public void drop(DropTargetEvent event) {
				if (RolesTransfer.getInstance().isSupportedType(
						event.currentDataType)) {
					Object[] roles = (Object[]) event.data;
					if (roles == null) return;
					TreeItem targetItem = (TreeItem) event.item;
					Resource resource;
					
					if (targetItem != null && (targetItem.getData()
							instanceof AdaptableResourceWrapper)) {
						AdaptableResourceWrapper wrapper = 
							(AdaptableResourceWrapper)targetItem.getData();
						resource = wrapper.getData();
					} else if (targetItem != null && (targetItem.getData()
							instanceof AdaptableRoleInstanceWrapper)) {
						AdaptableRoleInstanceWrapper wrapper = 
							(AdaptableRoleInstanceWrapper)targetItem.getData();
						resource = (Resource) wrapper.getRolesHolder();
					} else {
						resource = createResource(getResources());
					}
					for (Object role : roles) addRole(resource, (Role)role);
				} else if (ResourcesTransfer.getInstance().isSupportedType(
						event.currentDataType)) {
					Object[] resources = (Object[]) event.data;
					if (resources == null) return;
					
					for (Object object : resources) {
						Resource resource = (Resource) object;
						copy(getResources(), resource);
					}
				} else if (RolesInstancesTransfer.getInstance().isSupportedType(
						event.currentDataType)) {
					Object[] roles = (Object[]) event.data;
					if (roles == null) return;
					TreeItem targetItem = (TreeItem) event.item;
					Resource resource;
					
					if (targetItem != null && (targetItem.getData()
							instanceof AdaptableResourceWrapper)) {
						AdaptableResourceWrapper wrapper = 
							(AdaptableResourceWrapper)targetItem.getData();
						resource = wrapper.getData();
					} else if (targetItem != null && (targetItem.getData()
							instanceof AdaptableRoleInstanceWrapper)) {
						AdaptableRoleInstanceWrapper wrapper = 
							(AdaptableRoleInstanceWrapper)targetItem.getData();
						resource = (Resource) wrapper.getRolesHolder();
					} else {
						resource = createResource(getResources());
					}
					
					/* TODO : Check if we do not move to the same resource */
					for (Object object : roles) {
						copy(resource, (RoleInstance) object);
					}
				}
			}
		});
	}
	
	public void addResource(String name, boolean force) {
		// TODO : should check if resource with given name exists
//		while (resources.containsKey(name)) {
//			if (!force) return;
//			name = UITexts.get(MessageCode.RESOURCES_EDITOR_COPY) + name;
//		}

		Resource resource = new Resource(name, new LinkedHashSet<RoleInstance>());
		
		AddMemberCommand command = new AddMemberCommand(getResources(), resource);
		getCommandStack().execute(command);
	}
	
	protected Resource createResource(Resources resources) {
		Resource resource = new Resource();
		AddMemberCommand command = new AddMemberCommand(resources, resource);
		getCommandStack().execute(command);
		return resource;
	}
	
	private void addRole(Resource resource, Role role) {
		AddMemberCommand command = new AddMemberCommand(resource, new RoleInstance(role));
		getCommandStack().execute(command);
	}

	@SuppressWarnings("unchecked")
	private void copy(IHolder holder, IMember member) {
		AddMemberCommand command = new AddMemberCommand(holder, member.copy());
		getCommandStack().execute(command);
	}
	
	private void delete(IMember member) {
		DeleteMemberCommand command = new DeleteMemberCommand(member);
		getCommandStack().execute(command);
	}
	
	public void setLayoutData(Object layoutData) {
		treeViewer.getTree().setLayoutData(layoutData);
	}
}
